<template>
    <div class="affix" ref="root" :style="rootStyle">
        <div class="affix-content" :class="{ fixed }" :style="affixStyle">
            <slot/>
        </div>
    </div>
</template>

<script setup lang="ts">
    import type {CSSProperties} from "vue";
    import {getScrollContainer} from "@/untils/untils";
    import {shallowRef, ref, onMounted, watchEffect, computed, watch} from "vue";
    import {useEventListener,useWindowSize} from "@vueuse/core"
    import {useElementBounding} from "@/untils/auntils.js";
    //useWindowSize [获取窗口的宽高信息]
    //useElementBounding [获取元素的位置和长宽信息]
    //useEventListener [注册事件监听]
    // 目标元素
    const root = shallowRef<HTMLElement>();
    const fixed = ref(false);
    // 目标元素信息
    const {
        height: rootHeight,
        width: rootWidth,
        top: rootTop,
        bottom: rootBottom,
        update: updateRoot,
    } = useElementBounding(root); //获取元素的位置和长宽信息
    const {height: windowHeight} = useWindowSize();
    //  指定父容器
    const target = shallowRef<HTMLElement>();
    // 指定父容器的位置/长宽信息
    const targetRect = useElementBounding(target);
    const transform = ref(0);
    // 滚动容器
    const scrollContainer = shallowRef<HTMLElement | Window>();
    const emit = defineEmits<{
        (event: "change", fixed: boolean): void;
        (event: "scroll", value: { scrollTop: number; fixed: boolean }): void;
    }>();
    const rootStyle = computed<CSSProperties>(() => {
        return {
            width: fixed.value ? `${rootWidth.value}px` : '',
            height: fixed.value ? `${rootHeight.value}px` : '',
        };
    });
    const affixStyle = computed(() => {
        if (!fixed.value) return {};
        return {
            width: `${rootWidth.value}px`,
            height: `${rootHeight.value}px`,
            top: `${props.offset}px`,
            zIndex: props.zIndex,
        };
    });
    const props = withDefaults(defineProps<{
            offset?: number; zIndex?: number; position?: "top" | "bottom";// 注意：target不可以为滚动容器
            target?: string;
        }>(), {offset: 0, zIndex: 100, position: "top"}
    );
    onMounted(() => {
        scrollContainer.value = getScrollContainer(root.value!, true);
        if (props.target) {
            target.value =
                document.querySelector<HTMLElement>(props.target) ?? undefined;
            if (!target.value) {
                throw new Error("Target is not existed");
            }
        } else {
            // 如果没有默认设置为document.documentElement
            target.value = document.documentElement;
        }
        // 更新root的信息
        updateRoot()
    });
    watch(fixed, (v) => emit("change", v));

    const update = () => {
        if (!scrollContainer.value) return;
        if (props.position === "top") {
            if (props.target) {
                const difference =
                    targetRect.bottom.value - props.offset - rootHeight.value;
                transform.value = difference < 0 ? difference : 0;
                const {top} = targetRect.update(true);
                rootTop.value = top.value;
                fixed.value = props.offset > rootTop.value && targetRect.bottom.value > 0;
            } else {
                fixed.value = props.offset >= rootTop.value;
            }
        } else if (props.target) {
            fixed.value =
                windowHeight.value - props.offset < rootBottom.value &&
                windowHeight.value > targetRect.top.value;
            const difference =
                windowHeight.value -
                targetRect.top.value -
                props.offset -
                rootHeight.value;
            transform.value = difference < 0 ? -difference : 0;
        } else {
            fixed.value = windowHeight.value - props.offset < rootBottom.value;
        }
    };
    const handleScroll = () => {
        const scrollTop = scrollContainer.value instanceof Window ? document.documentElement.scrollTop : scrollContainer.value?.scrollTop;
        emit("scroll", {fixed: fixed.value, scrollTop: scrollTop || 0});
    };

    useEventListener(scrollContainer, "scroll", handleScroll);
    watchEffect(update);

    defineExpose({
        update,
        targetRect
    });
</script>

<style scoped lang="scss">
    .fixed {
        position: fixed;
    }
</style>
